package com.tuniu.agents.util;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;

import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

public class WebClientManager implements HttpManager {

    private final WebClient webClient;
    private final Duration timeout;

    /**
     * Create WebClientManager using a custom WebClient.Builder
     *
     * @param webClientBuilder WebClient.Builder instance
     * @param timeout request timeout duration
     */
    public WebClientManager(WebClient.Builder webClientBuilder, Duration timeout) {
        this.webClient = webClientBuilder
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();
        this.timeout = timeout;
    }

    /**
     * Create WebClientManager with default configuration
     */
    public WebClientManager() {
        this(WebClient.builder(), Duration.ofSeconds(30));
    }

    /**
     * Create WebClientManager with a custom timeout
     *
     * @param timeout request timeout duration
     */
    public WebClientManager(Duration timeout) {
        this(WebClient.builder(), timeout);
    }

    @Override
    public <T> T get(String url, Object queryParams, Class<T> responseType) {
        return get(url, queryParams, responseType, false);
    }

    /**
     * Send a GET request and return the deserialized object
     * Use CompletableFuture to avoid blocking in the reactive thread
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T get(String url, Object queryParams, Class<T> responseType, boolean encodeParams) {
        return get(url, queryParams, responseType, encodeParams, (HttpHeaders) null);
    }

    /**
     * Send a GET request and return the deserialized object
     * Use CompletableFuture to avoid blocking in the reactive thread
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T get(String url, Object queryParams, Class<T> responseType, boolean encodeParams, HttpHeaders headers) {
        try {
            return getAsync(url, queryParams, responseType, encodeParams, headers)
                    .subscribeOn(Schedulers.boundedElastic())
                    .toFuture()
                    .get();
        } catch (Exception e) {
            throw new RuntimeException("Error executing GET request: " + e.getMessage(), e);
        }
    }

    /**
     * Send a GET request and return the deserialized object
     * Use CompletableFuture to avoid blocking in the reactive thread
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T get(String url, Object queryParams, Class<T> responseType, boolean encodeParams, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return get(url, queryParams, responseType, encodeParams, httpHeaders);
    }

    /**
     * Send a POST request and return the deserialized object
     * Use CompletableFuture to avoid blocking in the reactive thread
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T post(String url, Object body, Class<T> responseType) {
        return post(url, body, responseType, (HttpHeaders) null);
    }

    /**
     * Send a POST request and return the deserialized object
     * Use CompletableFuture to avoid blocking in the reactive thread
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T post(String url, Object body, Class<T> responseType, HttpHeaders headers) {
        return post(url, body, responseType, headers, false, false);
    }

    /**
     * Send a POST request and return the deserialized object
     * Use CompletableFuture to avoid blocking in the reactive thread
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T post(String url, Object body, Class<T> responseType, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return post(url, body, responseType, httpHeaders);
    }

    /**
     * Send a POST request and return the deserialized object
     * Use CompletableFuture to avoid blocking in the reactive thread
     *
     * @param url           request URL
     * @param body          request body (object)
     * @param responseType  response content type
     * @param headers       custom request headers
     * @param encodeBody    whether to Base64 encode the request body
     * @param decodeResponse whether to Base64 decode the response
     * @param <T>           generic type
     * @return deserialized response object
     */
    @Override
    public <T> T post(String url, Object body, Class<T> responseType, HttpHeaders headers, boolean encodeBody, boolean decodeResponse) {
        try {
            return postAsync(url, body, responseType, headers, encodeBody, decodeResponse)
                    .subscribeOn(Schedulers.boundedElastic())
                    .toFuture()
                    .get();
        } catch (Exception e) {
            throw new RuntimeException("Error executing POST request: " + e.getMessage(), e);
        }
    }

    /**
     * Send a POST request and return the deserialized object
     * Use CompletableFuture to avoid blocking in the reactive thread
     *
     * @param url           request URL
     * @param body          request body (object)
     * @param responseType  response content type
     * @param headers       custom request headers (Map format)
     * @param encodeBody    whether to Base64 encode the request body
     * @param decodeResponse whether to Base64 decode the response
     * @param <T>           generic type
     * @return deserialized response object
     */
    @Override
    public <T> T post(String url, Object body, Class<T> responseType, Map<String, String> headers, boolean encodeBody, boolean decodeResponse) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return post(url, body, responseType, httpHeaders, encodeBody, decodeResponse);
    }

    /**
     * Asynchronously send a GET request
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> getAsync(String url, Object queryParams, Class<T> responseType, boolean encodeParams) {
        return getAsync(url, queryParams, responseType, encodeParams, (HttpHeaders) null);
    }

    /**
     * Asynchronously send a GET request
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> getAsync(String url, Object queryParams, Class<T> responseType, boolean encodeParams, HttpHeaders headers) {
        return getAsync(url, queryParams, responseType, encodeParams, headers, false);
    }

    /**
     * Asynchronously send a GET request
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> getAsync(String url, Object queryParams, Class<T> responseType, boolean encodeParams, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return getAsync(url, queryParams, responseType, encodeParams, httpHeaders);
    }

    public <T> Mono<T> getAsync(String url, Object queryParams, Class<T> responseType, boolean encodeParams, HttpHeaders headers, boolean decodeResponse) {
        Mono<String> responseMono;

        if (queryParams == null) {
            responseMono = webClient.get()
                    .uri(url)
                    .headers(httpHeaders -> {
                        if (headers != null) {
                            httpHeaders.addAll(headers);
                        }
                    })
                    .retrieve()
                    .bodyToMono(String.class)
                    .timeout(timeout);
        } else if (encodeParams) {
            // Convert query parameters to JSON string
            String queryContent = JsonUtil.toJson(queryParams);
            // Use UriComponentsBuilder (safer way)
            URI uri = UriComponentsBuilder.fromUriString(url)
                    .query(encodeBase64(queryContent))  // Set as query string without parameter name
                    .build()
                    .toUri();
            responseMono = webClient.get()
                    .uri(uri)
                    .headers(httpHeaders -> {
                        if (headers != null) {
                            httpHeaders.addAll(headers);
                        }
                    })
                    .retrieve()
                    .bodyToMono(String.class)
                    .timeout(timeout);
        } else {
            MultiValueMap<String, String> multiValueMap = toMultiValueMap(queryParams);
            responseMono = webClient.get()
                    .uri(uriBuilder -> uriBuilder.path(url).queryParams(multiValueMap).build())
                    .headers(httpHeaders -> {
                        if (headers != null) {
                            httpHeaders.addAll(headers);
                        }
                    })
                    .retrieve()
                    .bodyToMono(String.class)
                    .timeout(timeout);
        }

        return responseMono.map(response -> {
            if (decodeResponse) {
                response = decodeBase64(response);
            }
            return JsonUtil.fromJson(response, responseType);
        });
    }

    /**
     * Asynchronously send a GET request
     *
     * @param url           request URL
     * @param queryParams   query parameters (object)
     * @param responseType  response content type
     * @param encodeParams  whether to Base64 encode the query parameters
     * @param headers       custom request headers (Map format)
     * @param decodeResponse whether to Base64 decode the response
     * @param <T>           generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> getAsync(String url, Object queryParams, Class<T> responseType, boolean encodeParams, Map<String, String> headers, boolean decodeResponse) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return getAsync(url, queryParams, responseType, encodeParams, httpHeaders, decodeResponse);
    }

    /**
     * Asynchronously send a POST request
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsync(String url, Object body, Class<T> responseType) {
        return postAsync(url, body, responseType, (HttpHeaders) null);
    }

    /**
     * Asynchronously send a POST request
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsync(String url, Object body, Class<T> responseType, HttpHeaders headers) {
        return postAsync(url, body, responseType, headers, false, false);
    }

    /**
     * Asynchronously send a POST request
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsync(String url, Object body, Class<T> responseType, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return postAsync(url, body, responseType, httpHeaders);
    }

    /**
     * Asynchronously send a POST request
     *
     * @param url           request URL
     * @param body          request body (object)
     * @param responseType  response content type
     * @param headers       custom request headers
     * @param encodeBody    whether to Base64 encode the request body
     * @param decodeResponse whether to Base64 decode the response
     * @param <T>           generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsync(String url, Object body, Class<T> responseType, HttpHeaders headers, boolean encodeBody, boolean decodeResponse) {
        WebClient.RequestHeadersSpec<?> request;

        if (body != null) {
            String bodyContent = JsonUtil.toJson(body);
            if (encodeBody) {
                bodyContent = encodeBase64(bodyContent);
            }

            request = webClient.post()
                    .uri(url)
                    .headers(httpHeaders -> {
                        if (headers != null) {
                            httpHeaders.addAll(headers);
                        }
                    })
                    .body(BodyInserters.fromValue(bodyContent));
        } else {
            request = webClient.post()
                    .uri(url)
                    .headers(httpHeaders -> {
                        if (headers != null) {
                            httpHeaders.addAll(headers);
                        }
                    });
        }

        return request.retrieve()
                .bodyToMono(String.class)
                .timeout(timeout)
                .map(response -> {
                    if (decodeResponse) {
                        response = decodeBase64(response);
                    }
                    return JsonUtil.fromJson(response, responseType);
                });
    }

    /**
     * Asynchronously send a POST request
     *
     * @param url           request URL
     * @param body          request body (object)
     * @param responseType  response content type
     * @param headers       custom request headers (Map format)
     * @param encodeBody    whether to Base64 encode the request body
     * @param decodeResponse whether to Base64 decode the response
     * @param <T>           generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsync(String url, Object body, Class<T> responseType, Map<String, String> headers, boolean encodeBody, boolean decodeResponse) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return postAsync(url, body, responseType, httpHeaders, encodeBody, decodeResponse);
    }

    /**
     * Send a POST request with Base64 encoded body
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T postWithBase64Body(String url, Object body, Class<T> responseType) {
        return post(url, body, responseType, (HttpHeaders) null, true, false);
    }

    /**
     * Send a POST request with Base64 encoded body and decode Base64 response
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T postWithBase64BodyAndResponse(String url, Object body, Class<T> responseType) {
        return post(url, body, responseType, (HttpHeaders) null, true, true);
    }

    /**
     * Send a POST request with Base64 encoded body and custom request headers
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T postWithBase64Body(String url, Object body, Class<T> responseType, HttpHeaders headers) {
        return post(url, body, responseType, headers, true, false);
    }

    /**
     * Send a POST request with Base64 encoded body and custom request headers (Map format)
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T postWithBase64Body(String url, Object body, Class<T> responseType, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return postWithBase64Body(url, body, responseType, httpHeaders);
    }

    /**
     * Send a POST request with Base64 encoded body and custom request headers, and decode Base64 response
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return deserialized response object
     */
    public <T> T postWithBase64BodyAndResponse(String url, Object body, Class<T> responseType, HttpHeaders headers) {
        return post(url, body, responseType, headers, true, true);
    }

    /**
     * Send a POST request with Base64 encoded body and custom request headers (Map format), and decode Base64 response
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T postWithBase64BodyAndResponse(String url, Object body, Class<T> responseType, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return postWithBase64BodyAndResponse(url, body, responseType, httpHeaders);
    }

    /**
     * Send a GET request and decode Base64 response
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T getWithBase64Response(String url, Object queryParams, Class<T> responseType, boolean encodeParams) {
        try {
            return getAsync(url, queryParams, responseType, encodeParams, (HttpHeaders) null, true)
                    .subscribeOn(Schedulers.boundedElastic())
                    .toFuture()
                    .get();
        } catch (Exception e) {
            throw new RuntimeException("Error executing GET request: " + e.getMessage(), e);
        }
    }

    /**
     * Send a GET request and decode Base64 response
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T getWithBase64Response(String url, Object queryParams, Class<T> responseType, boolean encodeParams, HttpHeaders headers) {
        try {
            return getAsync(url, queryParams, responseType, encodeParams, headers, true)
                    .subscribeOn(Schedulers.boundedElastic())
                    .toFuture()
                    .get();
        } catch (Exception e) {
            throw new RuntimeException("Error executing GET request: " + e.getMessage(), e);
        }
    }

    /**
     * Send a GET request and decode Base64 response
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T getWithBase64Response(String url, Object queryParams, Class<T> responseType, boolean encodeParams, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return getWithBase64Response(url, queryParams, responseType, encodeParams, httpHeaders);
    }

    /**
     * Send a GET request, parameters and responses can choose whether to perform Base64 encoding/decoding
     *
     * @param url            request URL
     * @param queryParams    query parameters (object)
     * @param responseType   response content type
     * @param encodeParams   whether to Base64 encode the query parameters
     * @param decodeResponse whether to Base64 decode the response
     * @param <T>            generic type
     * @return deserialized response object
     */
    @Override
    public <T> T get(String url, Object queryParams, Class<T> responseType, boolean encodeParams, boolean decodeResponse) {
        try {
            return getAsync(url, queryParams, responseType, encodeParams, (HttpHeaders) null, decodeResponse)
                    .subscribeOn(Schedulers.boundedElastic())
                    .toFuture()
                    .get();
        } catch (Exception e) {
            throw new RuntimeException("Error executing GET request: " + e.getMessage(), e);
        }
    }

    /**
     * Send a GET request, parameters and responses can choose whether to perform Base64 encoding/decoding
     *
     * @param url            request URL
     * @param queryParams    query parameters (object)
     * @param responseType   response content type
     * @param encodeParams   whether to Base64 encode the query parameters
     * @param decodeResponse whether to Base64 decode the response
     * @param headers        custom request headers
     * @param <T>            generic type
     * @return deserialized response object
     */
    @Override
    public <T> T get(String url, Object queryParams, Class<T> responseType, boolean encodeParams, boolean decodeResponse, HttpHeaders headers) {
        try {
            return getAsync(url, queryParams, responseType, encodeParams, headers, decodeResponse)
                    .subscribeOn(Schedulers.boundedElastic())
                    .toFuture()
                    .get();
        } catch (Exception e) {
            throw new RuntimeException("Error executing GET request: " + e.getMessage(), e);
        }
    }

    /**
     * Send a GET request, parameters and responses can choose whether to perform Base64 encoding/decoding
     *
     * @param url            request URL
     * @param queryParams    query parameters (object)
     * @param responseType   response content type
     * @param encodeParams   whether to Base64 encode the query parameters
     * @param decodeResponse whether to Base64 decode the response
     * @param headers        custom request headers (Map format)
     * @param <T>            generic type
     * @return deserialized response object
     */
    @Override
    public <T> T get(String url, Object queryParams, Class<T> responseType, boolean encodeParams, boolean decodeResponse, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return get(url, queryParams, responseType, encodeParams, decodeResponse, httpHeaders);
    }

    /**
     * Use Base64 encoding for request parameters to send a GET request
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T getWithBase64Params(String url, Object queryParams, Class<T> responseType) {
        return get(url, queryParams, responseType, true, false);
    }

    /**
     * Use Base64 encoding for request parameters to send a GET request and decode Base64 response
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T getWithBase64ParamsAndResponse(String url, Object queryParams, Class<T> responseType) {
        return get(url, queryParams, responseType, true, true);
    }

    /**
     * Use Base64 encoding for request parameters and custom request headers to send a GET request
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T getWithBase64Params(String url, Object queryParams, Class<T> responseType, HttpHeaders headers) {
        return get(url, queryParams, responseType, true, false, headers);
    }

    /**
     * Use Base64 encoding for request parameters and custom request headers (Map format) to send a GET request
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T getWithBase64Params(String url, Object queryParams, Class<T> responseType, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return getWithBase64Params(url, queryParams, responseType, httpHeaders);
    }

    /**
     * Use Base64 encoding for request parameters and custom request headers to send a GET request and decode Base64 response
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T getWithBase64ParamsAndResponse(String url, Object queryParams, Class<T> responseType, HttpHeaders headers) {
        return get(url, queryParams, responseType, true, true, headers);
    }

    /**
     * Use Base64 encoding for request parameters and custom request headers (Map format) to send a GET request and decode Base64 response
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return deserialized response object
     */
    @Override
    public <T> T getWithBase64ParamsAndResponse(String url, Object queryParams, Class<T> responseType, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return getWithBase64ParamsAndResponse(url, queryParams, responseType, httpHeaders);
    }

    /**
     * Asynchronously send a POST request with Base64 encoded body
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsyncWithBase64Body(String url, Object body, Class<T> responseType) {
        return postAsync(url, body, responseType, (HttpHeaders) null, true, false);
    }

    /**
     * Asynchronously send a POST request with Base64 encoded body and decode Base64 response
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsyncWithBase64BodyAndResponse(String url, Object body, Class<T> responseType) {
        return postAsync(url, body, responseType, (HttpHeaders) null, true, true);
    }

    /**
     * Asynchronously send a POST request with Base64 encoded body and custom request headers
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsyncWithBase64Body(String url, Object body, Class<T> responseType, HttpHeaders headers) {
        return postAsync(url, body, responseType, headers, true, false);
    }

    /**
     * Asynchronously send a POST request with Base64 encoded body and custom request headers (Map format)
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsyncWithBase64Body(String url, Object body, Class<T> responseType, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return postAsyncWithBase64Body(url, body, responseType, httpHeaders);
    }

    /**
     * Asynchronously send a POST request with Base64 encoded body and custom request headers, and decode Base64 response
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsyncWithBase64BodyAndResponse(String url, Object body, Class<T> responseType, HttpHeaders headers) {
        return postAsync(url, body, responseType, headers, true, true);
    }

    /**
     * Asynchronously send a POST request with Base64 encoded body and custom request headers (Map format), and decode Base64 response
     *
     * @param url          request URL
     * @param body         request body (object)
     * @param responseType response content type
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> postAsyncWithBase64BodyAndResponse(String url, Object body, Class<T> responseType, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return postAsyncWithBase64BodyAndResponse(url, body, responseType, httpHeaders);
    }

    /**
     * Asynchronously send a GET request and decode Base64 response
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> getAsyncWithBase64Response(String url, Object queryParams, Class<T> responseType, boolean encodeParams) {
        return getAsync(url, queryParams, responseType, encodeParams, (HttpHeaders) null, true);
    }

    /**
     * Asynchronously send a GET request and decode Base64 response
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param headers      custom request headers
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> getAsyncWithBase64Response(String url, Object queryParams, Class<T> responseType, boolean encodeParams, HttpHeaders headers) {
        return getAsync(url, queryParams, responseType, encodeParams, headers, true);
    }

    /**
     * Asynchronously send a GET request and decode Base64 response
     *
     * @param url          request URL
     * @param queryParams  query parameters (object)
     * @param responseType response content type
     * @param encodeParams whether to Base64 encode the query parameters
     * @param headers      custom request headers (Map format)
     * @param <T>          generic type
     * @return Mono containing the response object
     */
    public <T> Mono<T> getAsyncWithBase64Response(String url, Object queryParams, Class<T> responseType, boolean encodeParams, Map<String, String> headers) {
        HttpHeaders httpHeaders = convertMapToHttpHeaders(headers);
        return getAsyncWithBase64Response(url, queryParams, responseType, encodeParams, httpHeaders);
    }

    /**
     * Convert Map type headers to HttpHeaders
     *
     * @param headers Map type headers
     * @return HttpHeaders object
     */
    private HttpHeaders convertMapToHttpHeaders(Map<String, String> headers) {
        if (headers == null) {
            return null;
        }
        HttpHeaders httpHeaders = new HttpHeaders();
        headers.forEach(httpHeaders::add);
        return httpHeaders;
    }

    @Override
    public <T> T postForm(String url, Map<String, String> formData, Class<T> responseType) {
        return null;
    }

    @Override
    public <T> T postForm(String url, Map<String, String> formData, Class<T> responseType, HttpHeaders headers) {
        return null;
    }

    @Override
    public <T> T postForm(String url, Map<String, String> formData, Class<T> responseType, Map<String, String> headers, boolean decodeResponse) {
        return null;
    }

    @Override
    public <T> T postFormWithBase64Response(String url, Map<String, String> formData, Class<T> responseType) {
        return null;
    }

    @Override
    public <T> T postFormWithBase64Response(String url, Map<String, String> formData, Class<T> responseType, HttpHeaders headers) {
        return null;
    }

    @Override
    public <T> T postFormObject(String url, Object formObject, Class<T> responseType) {
        return null;
    }

    @Override
    public <T> T postFormObject(String url, Object formObject, Class<T> responseType, HttpHeaders headers) {
        return null;
    }

    /**
     * Convert an object to MultiValueMap
     *
     * @param object object
     * @return MultiValueMap
     */
    private MultiValueMap<String, String> toMultiValueMap(Object object) {
        LinkedMultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>();
        Map<String, Object> map = JsonUtil.toMap(JsonUtil.toJson(object));
        map.forEach((key, value) -> multiValueMap.add(key, value != null ? value.toString() : null));
        return multiValueMap;
    }

    /**
     * Base64 encode a string
     *
     * @param input input string
     * @return Base64 encoded string
     */
    private String encodeBase64(String input) {
        return Base64.getEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * Decode a Base64 encoded string
     *
     * @param input Base64 encoded string
     * @return decoded string
     */
    private String decodeBase64(String input) {
        byte[] decodedBytes = Base64.getDecoder().decode(input);
        return new String(decodedBytes, StandardCharsets.UTF_8);
    }


    @Override
    public <T> T postFileObject(String url, Object formObject, MultipartFile file, Class<T> responseType,
            Map<String, String> headers, boolean decodeResponse) {
        // TODO Auto-generated method stub
        return null;
    }

}